/* 
 * BarChart, A simple multi source loadable class.
 *
 *	Written by: Joe Freeman 7/92
 *	
 *	HSB color sweep stolen from some of Randy Nelson's code
 *		should use NXEqualColor() for duplicates
 *
 */


#import "ChartOfMatrix.h"
#import <dpsclient/psops.h>
#import <c.h>
#import <stdio.h>

#define NUM_BOGUS	8	/* number of bars to draw when no data */
#define COM_VERSION	3.3	/* version 3 ported to 3.0 */

@implementation ChartOfMatrix


/*============================================================
 *factory
 *============================================================*/


+ initialize
{
	[super initialize];
	[ChartOfMatrix setVersion:2];
	return self;
}

- (const char *)getInspectorClassName 
{ 
	return "ChartOfMatrixInspector"; }

- initFrame:(NXRect *)r
{
	self = [super initFrame:r];
	minSheetSet = 0.0;
	maxSheetSet = 1.0;
	COM_Flags.autoScale = YES;
	COM_Flags.drawType = DRAW_V_BAR;
	backgroundColor = NX_COLORWHITE;
	highlightColor = NX_COLORBLACK;
	highlightIndex = MAXINT;		/* don't show on default */
	COM_Flags.drawFrame = YES;
	hMargin = vMargin = 15.0;
	hMargin = vMargin = 5.0;
	numPrototypes = 5;
	COM_Flags.randomBarColors = YES;
	return self;
}

- awake
{
    [super awake];
    highlightIndex = MAXINT;
    [self registerForDraggedTypes:&NXColorPboardType count:1];
    return self;
}

/*============================================================
 * color dragging support
 *============================================================*/

- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
    if ([sender draggingSourceOperationMask] & NX_DragOperationGeneric) {
	return NX_DragOperationGeneric;
    } else {
	return NX_DragOperationNone;
	    }
    }

- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
{
    NXColor c = NXReadColorFromPasteboard([sender draggingPasteboard]);
    [self setBackgroundColor: c];
    return YES;
}
/*============================================================
 * instance set / query methods
 *============================================================*/

- setDataSrc:anObject
{
    dataSrc = anObject;
    return self;
}

- setGraphType:(int)drawCode
	{ COM_Flags.drawType = drawCode;  [self update]; return self; }
- (int)graphType
	{ return COM_Flags.drawType; }

- takeRandomColorStateFrom:sender
	{ [self setRandomBarColorEnabled:[sender state]]; return self; }
- setRandomBarColorEnabled:(BOOL)flag
	{ COM_Flags.randomBarColors = flag; [self update]; return self; }
- (BOOL)isRandomBarColorEnabled
	{ return COM_Flags.randomBarColors; }
	
- takeBackgroundColorFrom:sender
	{ [self setBackgroundColor:  [sender color]]; return self; }
- setBackgroundColor:(NXColor)aColor
	{  backgroundColor = aColor; [self update]; return self; }
- (NXColor)backgroundColor
	{ return backgroundColor; }
	
- takeHighlightColorFrom:sender
	{ [self setHighlightColor:  [sender color]]; return self; }
- setHighlightColor:(NXColor)aColor
	{  highlightColor = aColor; [self update]; return self; }
- (NXColor)highlightColor
	{ return highlightColor; }


/* set and query the size for the margins (in points) */
- takeHMarginFrom:sender
	{ hMargin = [sender floatValue]; [self update]; return self; }
- takeVMarginFrom:sender
	{ vMargin = [sender floatValue]; [self update]; return self; }
- (float)hMargin
	{ return hMargin; }
- (float)vMargin
	{ return vMargin; }

- takeAutoScaleStateFrom:sender
	{ COM_Flags.autoScale = [sender floatValue]; return self; }
- setAutoScale:(BOOL)flag
	{ COM_Flags.autoScale = flag; [self update]; return self; }
- (BOOL)autoScale
	{ return COM_Flags.autoScale; }

/* fancy controls */
- takeFrameStateFrom:sender
	{ COM_Flags.drawFrame = [sender state]; [self update]; return self; }
- (BOOL)frameState
	{ return COM_Flags.drawFrame; }

- takeNumProtosFrom:sender
	{ numPrototypes= [sender intValue]; [self update]; return self; }
- (int)numProtos
	{ return numPrototypes; }

- takeBorderTypeFrom:sender
	{ borderType = [sender tag]; [self update]; return self; }
- (int)borderType
	{ return borderType; }

/* when autoscale is off, set the min and max for the sheet */
- takeMinValueFrom:sender
	{ minSheetSet = [sender floatValue]; return self; }
- takeMaxValueFrom:sender
	{ maxSheetSet = [sender floatValue]; return self; }
- (double)minValue
	{ return minSheetSet; }
- (double)maxValue
	{ return maxSheetSet; }

  
/*============================================================
 * target/action
 *============================================================*/

- (BOOL)acceptsFirstResponder { return YES; }

- copy:sender
{
	id pb = [Pasteboard new];	/* global pasteboard object */
	NXStream	*st;		/* stream to collect data in */
	char		*data;		/* actual data buffer */
	int		length;		/* length of data */
	int		maxLength;	/* (not used here) */
	
	/* declare that we will supply  a single type of data, eps */
	[pb declareTypes:&NXPostScriptPboard num:1 owner:self];
	
	/* get a stream which writes to memory */
	st = NXOpenMemory (NULL, 0, NX_WRITEONLY);
	
	/* find bounding box and then write it to the stream */
	[self copyPSCodeInside:&bounds to:st];
	
	/* get actuall data buffer form stream */
	NXGetMemoryBuffer (st, &data, &length, &maxLength);
	
	/* write data to pasteboard */
	[pb writeType:NXPostScriptPboard data:data length:length ];
	
	/* dealocate stream including it's buffer */
	NXCloseMemory (st, NX_FREEBUFFER );
	

    return self;
}

/*============================================================
 * do real work
 *============================================================*/

- (int)numLocations
{
    int numRows,numCols;

    if ([dataSrc respondsTo:@selector(getValue:forProperty:at:)]){
    	return [dataSrc count];
    } else if ([dataSrc respondsTo:@selector(selectedCell)]){
    	[dataSrc getNumRows:&numRows numCols:&numCols];
	return MAX(numRows,numCols);
    } else if (!dataSrc) {
    	/* put up some dummy graph on palette */
	return NUM_BOGUS;
    }
    return 0;
}

- (float)valueOfLocation:(int)n
{
    static 	float	data[NUM_BOGUS] = {.1, .3, .2, .7, .4, .8, .5, .98};
    int numRows,numCols;
    float theValue;
    id	  mrValue = [[DBValue alloc] init];

    if ([dataSrc respondsTo:@selector(getValue:forProperty:at:)]){
   	[dataSrc getValue:mrValue forProperty:mrExpression at:n];
	theValue = [mrValue floatValue];
	[mrValue free];
	return theValue;
    } else if ([dataSrc respondsTo:@selector(selectedCell)]){
    	[dataSrc getNumRows:&numRows numCols:&numCols];
	if (numCols == 1) {
		return [[dataSrc cellAt:n :0] floatValue];
	} else { 	/* numRows == 1 */
		return [[dataSrc cellAt:0 :n] floatValue];
	}
    } else if (!dataSrc) {
    	return data[n];
    }
    
    return 0.0;
}

/*============================================================
 * target action loading
 *============================================================*/

- plotFromMatrix:sender
{
    if (!dataSrc && [sender respondsTo:@selector(selectedCell)]){
    	[self setDataSrc:sender];
    } 
    [self update];
    return self;
}

/*============================================================
 * dbKit support
 *============================================================*/

- associationContentsDidChange:association
{
    mrFetchGroup = 	[association fetchGroup];
    dataSrc = 		[mrFetchGroup recordList];
    highlightIndex = 	[mrFetchGroup currentRecord];
    mrExpression = 	[association expression];
    [self update];
    return self;
}

/*
** adh 7/27/92
** Do this so we redraw when values are updated in the UI
*/
- association:association setValue:(DBValue *)value
{
    return [self update];
}

- associationSelectionDidChange:association
{
    /* assume fetchgroup doesn't change so don't update mrFetchGroup */
    highlightIndex =  [[association fetchGroup] currentRecord];
    return [self update];
}

- associationCurrentRecordDidDelete:association
{
    /* assumes record list is the same */
    return [self update];
    return self;
}

/*============================================================
 * do a selection with the mouse
 *============================================================*/
 
- mouseDown:(NXEvent *)theEvent
{
    NXEvent 	lastEvent;
    NXRect 	rectOfBar;
    NXRect	drawRect;
    int 	longSize = [self numLocations];
    int		index;

    
    if ((COM_Flags.drawType != DRAW_H_BAR && 
    			COM_Flags.drawType != DRAW_V_BAR ) ||
		![dataSrc respondsTo:@selector(getValue:forProperty:at:)]) {
    	NXBeep();
	return nil;
    }
    
    lastEvent = *theEvent;
    [self convertPoint: &lastEvent.location fromView:nil];
    /* modify the mouse position to be in psuedo drawing area coords */
    lastEvent.location.x -= hMargin;
    lastEvent.location.y -= vMargin;

    /* allow for the margins when calc'ing the bar moused down on */
    drawRect = bounds;
    drawRect.origin.x += 	hMargin;
    drawRect.origin.y += 	vMargin;
    drawRect.size.width -= 	(2*hMargin);
    drawRect.size.height -= 	(2*vMargin);
    
    for ( index = 0 ; index < longSize; index++){
    	if (COM_Flags.drawType == DRAW_V_BAR) {
		[self calcRect:&rectOfBar ofBar:index
			insideRect:&drawRect
			usingMin:NX_Y(&bounds) ];
		rectOfBar.origin.y = bounds.origin.y;
		rectOfBar.size.height = bounds.size.height;
	} else  if (COM_Flags.drawType == DRAW_H_BAR) {
		[self calcRect:&rectOfBar ofBar:index
			insideRect:&drawRect
			usingMin:NX_X(&bounds) ];
		rectOfBar.origin.x = bounds.origin.x;
		rectOfBar.size.width = bounds.size.width;
	} else {
		return nil;
	}
	if ([self mouse:&lastEvent.location inRect:&rectOfBar]){
		[mrFetchGroup setCurrentRecord:index];
		break;
	}
    }
    return self;
} 

/*============================================================
 * target/action
 *============================================================*/

- read:(NXTypedStream *)stream
{
        int tmpScale, tmpDrawFrame, tmpDrawType;
	[super read:stream];
	dataSrc =  NXReadObject(stream);
	minField = NXReadObject(stream);
	maxField =  NXReadObject(stream);
	meanField = NXReadObject(stream);
	/* the first demo palette went out as version 1 */
	if (NXTypedStreamClassVersion(stream, "ChartOfMatrix")<2) {
		NXReadTypes(stream,"ffffiii",
					&minSheetSet,&maxSheetSet,
					&vMargin,&hMargin,
					&tmpScale,
					&tmpDrawType,
					&tmpDrawFrame);
		COM_Flags.autoScale = tmpScale;
		COM_Flags.drawType = tmpDrawType;
		COM_Flags.drawFrame = tmpDrawFrame;
		COM_Flags.randomBarColors = YES;
	} else {
		NXReadTypes(stream,"ffffi",
					&minSheetSet,&maxSheetSet,
					&vMargin,&hMargin,
					&COM_Flags);
	}
	
	backgroundColor = NXReadColor(stream);
	highlightColor = NXReadColor(stream);
	NXReadTypes(stream,"ii",
					&numPrototypes,
					&borderType);
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteObjectReference(stream, dataSrc);
	NXWriteObjectReference(stream, minField);
	NXWriteObjectReference(stream, maxField);
	NXWriteObjectReference(stream, meanField);
	NXWriteTypes(stream,"ffffi",
					&minSheetSet,&maxSheetSet,
					&vMargin,&hMargin,
					&COM_Flags);
	NXWriteColor(stream,		backgroundColor);
	NXWriteColor(stream,		highlightColor);
	NXWriteTypes(stream,"ii",
					&numPrototypes,
					&borderType);
	return self;
}

/*============================================================
 *display
 *============================================================*/

- calcMin:(float *)rMin andMax:(float *)rMax andMean:(float *)rMean;
{
	int	index;
	int	longSize;
	float	thisVal;
	float	sumAll = 0;

	*rMin = *rMax = [self valueOfLocation:0];
	longSize = [self numLocations];

	/* first figure out what the maxs and mins are */
	for ( index = 0 ; index < longSize; index++){
		thisVal = [self valueOfLocation:index];
		if (*rMin > thisVal)		*rMin = thisVal;
		if (*rMax < thisVal)		*rMax = thisVal;
		sumAll += thisVal;
	}
	*rMean = sumAll / longSize;
	
	[minField setFloatValue:*rMin];
	[maxField setFloatValue:*rMax];
	[meanField setFloatValue:*rMean];
	return self;
}

- renderVLines:(NXRect *)r  min:(float )minSheetVal max:(float )maxSheetVal
{
	int	longSize;	/* numRows or numCols, whichever is the long side */
	int	index;
	float	thisVal;
	float	cellWidth;	/* the width of a unit (N) to plot */

	/* scale the plot */
	PSscale(1.0, r->size.height / ( maxSheetVal - minSheetVal) );
	PStranslate(0.0, -minSheetVal);
		
	longSize = [self numLocations];
	cellWidth = r->size.width / (longSize);

	/* now plot each square */
	PSsetgray(NX_BLACK);
	thisVal = [self valueOfLocation:0];
	PSmoveto(cellWidth/2.0, thisVal);
	for ( index = 0 ; index < longSize; index++){
		thisVal = [self valueOfLocation:index];
		PSlineto(cellWidth*index+ cellWidth/2.0, thisVal);
	}
	PSstroke();
	return self;
}

/* we can position the bars anywhere inside the view by changing the
 * insideRect parameter 
 */
- calcRect:(NXRect *)r 
	ofBar:(int)n 
	insideRect:(NXRect *)boundingRect
	usingMin:(float)minSheetVal 
{
    float	thisVal;
    float	cellWidth;	/* width of a unit (N) to plot */

    thisVal = [self valueOfLocation:n];
    if (COM_Flags.drawType == DRAW_V_BAR){
	cellWidth = boundingRect->size.width / (3 * [self numLocations] + 1);
	r->origin.x = n * 3 * cellWidth + cellWidth;
	if (thisVal < 0.0){
				r->origin.y = thisVal;
				r->size.height = -thisVal;
	}else{
				r->origin.y = MAX(minSheetVal, 0.0);
				r->size.height= thisVal-r->origin.y;
	}
	r->size.width = 2 * cellWidth;
    } else /* assume h bar */ {
	cellWidth = boundingRect->size.height / (3 * [self numLocations] + 1);
	r->origin.y = n * 3 * cellWidth + cellWidth;
	if (thisVal < 0.0){
				r->origin.x = thisVal;
				r->size.width = -thisVal;
	}else{
				r->origin.x = MAX(minSheetVal, 0.0);
				r->size.width= thisVal - r->origin.x;
	}
	r->size.height = 2 * cellWidth;
    }

    return self;
}

/* this should probably be empty but because Kris asked for a bar/lines switch 
 * in the inspectorwe have the abillity to draw bars (vertical).  Of course  
 * this makes the vertical bar drawing object almost codeless
 */
- renderBars:(NXRect *)r min:(float )minSheetVal max:(float )maxSheetVal
{
	int	longSize;	/* numRows/numCols, whichever is long side */
	int	index;
	NXRect	barRect;	/* bounding rectangle for bar in the chart */
	NXColor	HSBColor;	/* in case randomBarColors */

	longSize = [self numLocations];
	if (COM_Flags.drawType == DRAW_V_BAR){
		/* scale the plot */
		PSscale(1.0, r->size.height / ( maxSheetVal - minSheetVal) );
		PStranslate(0.0, -minSheetVal);
	} else /* assume h bar */ {
		/* scale the plot */
		PSscale(r->size.width / ( maxSheetVal - minSheetVal), 1.0 );
		PStranslate( -minSheetVal, 0.0);
	}
		
	/* now plot each square */
	for ( index = 0 ; index < longSize; index++){
		if (index == highlightIndex)
			NXSetColor(highlightColor);
		else if (!COM_Flags.randomBarColors) {
			PSsetgray ((1.0 / (longSize+2.0)) * (index +1));
		} else {
			HSBColor = NXConvertHSBToColor(
	    			((float)index / (float)longSize), 
				1.0, 1.0);
			NXSetColor(HSBColor);
		}
		[self calcRect:&barRect ofBar:index
			insideRect:(NXRect *)r
			usingMin:minSheetVal];
		NXRectFill(&barRect);
	}
	return self;
}


/* spacing:	Each plot is a width of 2N and each gap is a width of 1N
 *		total width is m*(2N+N) + N  = 3mN+N = where m = number of bars
 *		Thus N = width / (3m+1)
 *
 * scaling:	The scaling can make some stuff look pretty funny.  
 */
- drawSelf:(NXRect *)r :(int)c
{	
    float	minCellVal, maxCellVal;	/* min & max of values to be plotted */
    float	meanCellVal;		/* mean of the plotted values */
    float	minSheetVal,maxSheetVal;/* RangeOfNumbers will plot in graph */
    NXRect	rectOfPlot;		/* bounds of rect that will hold plot */
    NXSetColor(backgroundColor);
    NXRectFill(&bounds);
    PSsetgray(NX_BLACK);
    switch	(borderType) {
	case	NX_LINE:
			NXFrameRect(&bounds);
			break;
	case	NX_BEZEL:
			NXDrawWhiteBezel(&bounds,&bounds);
			break;
	case	NX_GROOVE:
			NXDrawGroove(&bounds, &bounds);
			break;
	default:	break;
    }
    rectOfPlot= bounds;
    rectOfPlot.origin.x += hMargin;
    rectOfPlot.origin.y += vMargin;
    rectOfPlot.size.width -=2*hMargin;
    rectOfPlot.size.height -=2*vMargin;
    if (		NX_WIDTH(&bounds) < (2*hMargin) || 
			NX_HEIGHT(&bounds) < (2*vMargin) ) 
		return self;
		
	
    [window disableFlushWindow];
    PSgsave();
    PSsetlinewidth(0.0);
    PStranslate (hMargin, vMargin);
    
    if (YES){
	/* great we only have a single axis to work on */
	[self calcMin:&minCellVal andMax:&maxCellVal andMean:&meanCellVal];

	if (COM_Flags.autoScale){
		/* give us a plot if they are all the same (but not 0.0)  */
		if (minCellVal == maxCellVal){
				if (minCellVal > 0.0)	minCellVal = 0.0;
				else if (maxCellVal < 0.0) maxCellVal = 0.0;
		}
		
		/* figure out what min and the max should be on the sheet */
		if (minCellVal == 0.0)	minSheetVal= 0.0;
		else minSheetVal= minCellVal - ((maxCellVal - minCellVal)*0.2);
		/* we shouldn't have pushed this across the origin */
		if (minSheetVal < 0.0 && minCellVal > 0.0) minSheetVal = 0.0;
		
		if (maxCellVal == 0.0)	maxSheetVal = 0.0;
		else maxSheetVal= maxCellVal +((maxCellVal - minCellVal)* 0.2);
		/* make sure we didn't go across the origin */
		if (maxSheetVal > 0.0 && maxCellVal < 0.0) maxSheetVal = 0.0;
	} else {
		minSheetVal = minSheetSet;
		maxSheetVal = maxSheetSet;
	}
		
	switch(COM_Flags.drawType){
	case	DRAW_H_BAR:
	case	DRAW_V_BAR:
		[self renderBars:&rectOfPlot min:minSheetVal max:maxSheetVal];
		break;
	case	DRAW_H_LINE:
		break;
	case	DRAW_V_LINE:
		[self renderVLines:&rectOfPlot min:minSheetVal max:maxSheetVal ];
		break;
	default:
		break;
	}

	PSgrestore();
	if (COM_Flags.drawFrame){
		PSsetgray(NX_BLACK);
		NXFrameRect(&rectOfPlot);
	}
    }
    [window reenableFlushWindow];
    return self;
}

@end
